/*
 Copyright (c) 2009 by contributors:

 * James Hight (http://labs.zavoo.com/)
 * Richard R. Masters
 * Google Inc. (Brad Neuberg -- http://codinginparadise.org)

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package org.svgweb.nodes;

	import flash.xml.XML;
	import haxe.PosInfos;
    import org.svgweb.core.SVGNode;
    import org.svgweb.core.SVGViewer;
    import org.svgweb.events.SVGEvent;
    import flash.geom.Matrix;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.Lib;
    import org.svgweb.utils.ImageLoader;

    class SVGSVGNode extends SVGNode {
        
        override public var xml(getXml, setXml): XML;
        var parentSVGRoot:SVGSVGNode ;
        var _pendingRenderCount:Int;
        var firedOnLoad:Bool ;
        var loadTime:Float ; // milliseconds
        var scaleModeParam:String ;

        var _idLookup:Dynamic;
        var _guidLookup:Dynamic;
        var _referersById:Hash<Array<SVGNode>>;
        var _fonts:Dynamic;
        var fontListeners:Array<Dynamic> ;


		public var title:String;
        
        public var viewer:SVGViewer;
        
        public var currentScale:Int ;
        public var currentTranslate:Dynamic ;
        public var imageCache:Dynamic ;
		
        var lastFrameTime:UInt ;
        var totalFrameTime:UInt ;
        var frameCount:UInt ;
        
        /** If this file was loaded from a URL, such as samples/scimitar.svg,
            then objectURL points to the relative path from which it was
            fetched, such as 'samples/' */
        public var objectURL:String ;
        
        /** Same as objectURL, but the relative path from the containing HTML
            page. */
        public var pageURL:String ;
        
        public function new(?svgRoot:SVGSVGNode = null, ?xml:XML = null, 
                                   ?original:SVGNode = null,
                                   ?viewer:SVGViewer = null, ?objectURL:String = '',
                                   ?pageURL:String = ''){
            parentSVGRoot = null;
            firedOnLoad = false;
            loadTime = -1;
            scaleModeParam = 'svg_all';
            fontListeners = new Array();
            currentScale = 1;
            currentTranslate = {x: 0, y: 0};
            imageCache = {};
            lastFrameTime = Lib.getTimer();
            totalFrameTime = 0;
            frameCount = 0;
            objectURL = '';
            pageURL = '';
            this.viewer = viewer;
            this.objectURL = objectURL;
            this.pageURL = pageURL;
            if (svgRoot != null) {
                this.parentSVGRoot = svgRoot;
            }
            super(this, xml, original);            
        }

        public override function setXml(value:XML):XML{
            //default xml namespace = svg;        
            this._idLookup = {};
            this._guidLookup = {};
            this._referersById = new Hash();
            this._fonts = {};    
            //super.xml = value;
            super.setXml(value);

            // If this is the top SVG element, then start the render tracking process.
            if (this.parentSVGRoot == null) {
               if ( null != this.xml) {
                    if (this.xml.attribute('id').length() == 1) {
                       Reflect.setField( this._idLookup, this.xml.attribute('id').toString(), this);
                    }
                    Reflect.setField( this._guidLookup, this.xml.attribute('__guid').toString(), this);
                }
                this._pendingRenderCount = 1;
            }
        	return value;
           }
        
        override function onAddedToStage(event:Event):Void {
            super.onAddedToStage(event);
            if (null == this.parentSVGRoot) {
                topSprite.addEventListener(Event.ENTER_FRAME, updateAnimations);
            }
        }

        override function onRemovedFromStage(event:Event):Void {
            if ( null == this.parentSVGRoot) {
                topSprite.removeEventListener(Event.ENTER_FRAME, updateAnimations);
            }
            super.onRemovedFromStage(event);
        }

        public function getDocTime():Float {
           if ( null != this.parentSVGRoot) {
                return this.parentSVGRoot.getDocTime();
            }
            else {
                return (Lib.getTimer() - this.loadTime) / 1000.0;
            }
        }

        function updateAnimations(event:Event):Void {
        /* Frame Counter
            if (this.lastFrameTime > 0) {
                this.totalFrameTime += (getTimer() - this.lastFrameTime);
                this.frameCount++;
                if (this.frameCount % 10 == 0) {
                    this.dbg("last frame time: " + (getTimer() - this.lastFrameTime) +
                         "ms, avg time: " + (this.totalFrameTime/this.frameCount) +
                         "ms avg rate: " + (1000.0/(this.totalFrameTime/this.frameCount)) + " frames/sec");
                    this.totalFrameTime = 0;
                    this.frameCount = 0;
                }
            }
            this.lastFrameTime=getTimer();
        */

            // Nothing to do while the document is loading
            if (this.loadTime == -1) {
                return;
            }
            var svgEvent:SVGEvent = new SVGEvent(SVGEvent._SVGDocTimeUpdate);
            svgEvent.setDocTime( (Lib.getTimer() - this.loadTime) / 1000.0 );
            this.dispatchEvent(svgEvent);
			//trace('dispatchedEvent: ' + svgEvent);
        }

        public function seekToDocTime(docTime:Float):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.seekToDocTime(docTime);
            }
            else {
                this.loadTime = Lib.getTimer() - docTime*1000.0;
                var svgEvent:SVGEvent = new SVGEvent(SVGEvent._SVGDocTimeSeek);
                svgEvent.setDocTime(docTime);
                this.dispatchEvent(svgEvent);
            }
        }

        public override function getStyle(name:String, ?defaultValue:Dynamic = null, ?inherit:Bool = false):Dynamic {
            var value:String = super.getStyle(name, null, false);
           if ( null != value) {
                return value;
            }

            if ((name == 'opacity') 
                || (name == 'fill-opacity')
                || (name == 'stroke-opacity')
                || (name == 'stroke-width')) {
                return '1';
            }

            if (name == 'fill') {
                return 'black';
            }

            if (name == 'stroke') {
                return 'none';
            }
            
            // Provide defaults for the various rendering hints.
            if (name == 'shape-rendering' || name == 'color-rendering'
                || name == 'text-rendering' || name == 'image-rendering') {
              return 'auto';
            }
			var testA:Array<Int> = [1, 2, 3];
			trace(testA.lastIndexOf(2));
            return defaultValue;
        }
        
        public override function getAttribute(name:String, ?defaultValue:Dynamic = null,
                                              ?inherit:Bool = true,
                                              ?applyAnimations:Bool = true,
                                              ?useStyle:Bool = false):Dynamic {
            var value:String = this._getAttribute(name, defaultValue, inherit, applyAnimations, useStyle);
            if (value != null) {
                return value;
            }

            if (SVGNode.ATTRIBUTES_NOT_INHERITED.indexOf(name) != -1) {
                if (defaultValue == null) {
                    if (name == 'opacity') {
                        return '1';
                    }
                    // default fall through
                }
                return defaultValue;
            }

            if (inherit && (this.svgParent != null))  {
                return this.svgParent.getAttribute(name, defaultValue, inherit, applyAnimations, useStyle);
            }

            if ((name == 'opacity') 
                || (name == 'fill-opacity')
                || (name == 'stroke-opacity')
                || (name == 'stroke-width')) {
                return '1';
            }

            if (name == 'fill') {
                return 'black';
            }

            if (name == 'stroke') {
                return 'none';
            }

            if (name == 'visibility') {
                return 'visible';
            }

            if (name == 'display') {
                return 'inline';
            }

            return defaultValue;
        }

        public override function handleAttrChange(name:String, value:String, ?attrNamespace:String = null):Void {
            super.handleAttrChange(name, value, attrNamespace);
            if (name == 'x' || name == 'y') {
                this.applyDefaultMask();
            }
        }

        override function loadAttribute(name:String, ?field:String = null,
                                         ?applyStyle:Bool = false):Void {
            // Issue 226: Topmost <svg> tag ignores x and y
            if (Std.is( topSprite.parent, SVGViewer) && 
                (name == 'x' || name == 'y') ) {
                return;
            } else {
                super.loadAttribute(name, field, applyStyle);
            }
        }

        // The following functions track the number of elements that have a redraw
        // pending. When the count reaches zero, the onLoad handler can be called.
        // 
        // The overall count starts at one to account for the top SVG element. This is done
        // in the set xml handler above.
        // Other elements increment the count when they are added. This is done
        // by an override of addChild in SVGNode.
        // Every element decrements the count when rendering is complete. This is done
        // by drawNode in SVGNode.
        public function renderPending():Void {
            if (null != this.parentSVGRoot) {
                this.parentSVGRoot.renderPending();
            }
            else {
                this._pendingRenderCount++;
            }
        }

        public function renderFinished():Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.renderFinished();
            }
            else {
                //trace(this._pendingRenderCount);
                this._pendingRenderCount--;
                if (this._pendingRenderCount == 0) {
                    if (!this.firedOnLoad) {
                        this.firedOnLoad = true;
                        this.handleOnLoad();
                    }
                }
                if (this._pendingRenderCount < 0 && !this.firedOnLoad) {
                    this.dbg("error: pendingRenderCount count negative: " + this._pendingRenderCount);
                }
            }
        }
        
        public function registerNode(node:SVGNode):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.registerNode(node);
            } else {
                registerID(node);
                registerGUID(node);
            }
        }
        
        public function isLoaded():Bool {
           if ( null != parentSVGRoot) {
                return parentSVGRoot.isLoaded();
            } else {
                return firedOnLoad;
            }
        }
        
        public override function addEventListener (type:String, listener:Dynamic->Void, ?useCapture:Bool = false,
                                                   ?priority:Int = 0, ?useWeakReference:Bool = false):Void {
            if (parentSVGRoot != null && (type == SVGEvent.SVGLoad ||
                                  type == SVGEvent._SVGDocTimeUpdate ||
                                  type == SVGEvent._SVGDocTimeSeek)) {
                parentSVGRoot.addEventListener(type, listener, useCapture, priority, useWeakReference);
            } else {
                super.addEventListener(type, listener, useCapture, priority, useWeakReference);
            }
        }
        
        public override function removeEventListener (type:String, listener:Dynamic->Void, ?useCapture:Bool = false):Void {
            if (null != parentSVGRoot && (type == SVGEvent.SVGLoad ||
                                  type == SVGEvent._SVGDocTimeUpdate ||
                                  type == SVGEvent._SVGDocTimeSeek)) {
                parentSVGRoot.removeEventListener(type, listener, useCapture);
            } else {
                super.removeEventListener(type, listener, useCapture);
            }
        }
        
        public function unregisterNode(node:SVGNode):Void {
            if (this.parentSVGRoot != null) {
                this.parentSVGRoot.unregisterNode(node);
            } else {
                unregisterID(node);
                unregisterGUID(node);
            }
        }
        
        public function registerID(node:SVGNode):Void {
           if ( null != parentSVGRoot) {
                parentSVGRoot.registerID(node);
            } else if ( null != node.id) {
                Reflect.setField(_idLookup, node.id, node);
            }
        }

        public function unregisterID(node:SVGNode):Void {
           if ( null != parentSVGRoot) {
                parentSVGRoot.unregisterID(node);
            } else if ( null != node.id) {
                Reflect.deleteField(_idLookup, node.id);
            }
        }
        
        public function registerGUID(node:SVGNode):Void {
           if ( null != parentSVGRoot) {
                parentSVGRoot.registerGUID(node);
            } else {
                Reflect.setField(_guidLookup, node.guid, node);
            }
        }

        public function unregisterGUID(node:SVGNode):Void {
           if ( null != parentSVGRoot) {
                parentSVGRoot.unregisterGUID(node);
            } else {
                Reflect.deleteField(_guidLookup, node.guid);
            }
        }

        override function registerSelf():Void {
            super.registerSelf();

           if ( null != parentSVGRoot) {
                parentSVGRoot.registerNode(this);
            }
        }

        override function unregisterSelf():Void {
            super.unregisterSelf();

           if ( null != parentSVGRoot) {
                parentSVGRoot.unregisterNode(this);
                parentSVGRoot = null;
            }
        }

        /**
         * 
         * If this object depends on another object, then we can
         * register our interest in being invalidated when the
         * dependency object is redrawn.
         * 
         * The node references referenceId. 
         * An array of nodes that reference the referenceId is created.
         * 
         **/
        public function addReference(node:SVGNode, referencedId:String):Void {
            if (this.parentSVGRoot != null) {
                this.parentSVGRoot.addReference(node, referencedId);
            }
            else {

                if (!this._referersById.exists(referencedId)) {
                     this._referersById.set(referencedId, new Array<SVGNode>());
                }
				var arr:Array<SVGNode> = this._referersById.get(referencedId);
                if (arr.lastIndexOf(node) == -1) {
                    this._referersById.get(referencedId).push(node);
                }
            }
        }  

        public function deleteReference(node:SVGNode, referencedId:String):Void {
            if (this.parentSVGRoot != null) {
                this.parentSVGRoot.deleteReference(node, referencedId);
            }
            else {
				var refsById:Array<SVGNode> = this._referersById.get( referencedId);
                if (_referersById.exists(referencedId)) {
                  /* if (_referersById.get( referencedId).lastIndexOf(node) != -1) {
                       // Reflect.deleteField(refsById[refsById.lastIndexOf(node)];
					   _referersById.get( referencedId).splice(_referersById.get( referencedId).lastIndexOf(node), 1);
                    }*/
					_referersById.get( referencedId).remove(node);
                }
            }
        }
        
        public function invalidateReferers(id:String):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.invalidateReferers(id);
            }
            else {
                //this.svgRoot.debug("Invalidating referers to "  + id);
                if (this._referersById.exists(id)) {
                    var referers:Array<SVGNode> = this._referersById.get(id);
                    for ( referer in referers) {
                        referer.invalidateDisplay();
                    }
                }
            }
        }

        public function getNode(name:String):SVGNode {
           if ( null != this.parentSVGRoot) {
                return this.parentSVGRoot.getNode(name);
            } else if (_idLookup.hasOwnProperty(name)) {
                return Reflect.field(_idLookup, name);
            }
            return null;
        }

        public function getNodeByGUID(guid:String):SVGNode {            
           if ( null != parentSVGRoot) {
                return parentSVGRoot.getNodeByGUID(guid);
            } else if (_guidLookup.hasOwnProperty(guid)) {
                return Reflect.field(_guidLookup, guid);
            }
            return null;
        }
        
        /**
         * 
         * Fonts
         *
         **/
		
        public function registerFont(font:SVGFontNode):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.registerFont(font);
             }
            else {
                Reflect.setField(_fonts, font.getFontFaceName(), font);

                for (node in fontListeners) {
                    node.onRegisterFont(font.getFontFaceName());
                }
            }
         }
         
        public function unregisterFont(font:SVGFontNode):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.unregisterFont(font);
            }
            else {
                Reflect.deleteField(_fonts, font.getFontFaceName());
            }
        }

        public function getFont(fontFace:String):SVGFontNode {
           if ( null != this.parentSVGRoot) {
                return this.parentSVGRoot.getFont(fontFace);
             }
            else {
                return Reflect.field(_fonts, fontFace);
            }
         }
		
        public function registerFontListener(node:SVGNode):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.registerFontListener(node);
            }
            else {
                fontListeners.push(node);
            }
        }

        public function unregisterFontListener(node:SVGNode):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.unregisterFontListener(node);
            }
            else {
                if (fontListeners.lastIndexOf(node) != -1) {
                    //Reflect.deleteField(fontListeners, fontListeners.lastIndexOf(node));
					fontListeners.splice(fontListeners.lastIndexOf(node), 1);
                }
            }
        }


        public function handleScript(script:String):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.handleScript(script);
            }
            else if (Std.is( topSprite.parent, SVGViewer)) {
                cast(topSprite.parent, SVGViewer).handleScript(script);
            }
        }

        public function loadImage(imageHref:String, imageNode:SVGImageNode):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.loadImage(imageHref, imageNode);
            }
            else {
                if (Reflect.field(imageCache, imageHref) == null) {
                    Reflect.setField(this.imageCache, imageHref, new ImageLoader(imageHref));
                }
                Reflect.field(imageCache, imageHref) .addListener(imageNode);
            }
        }


        public function handleOnLoad():Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.handleOnLoad();
            }
            else {
                this.loadTime = Lib.getTimer();
				trace('loadTime:' + loadTime);// + '\n _idLookup:' + Std.string(_idLookup) +
				//'\n _guidLookup:' + Std.string(_guidLookup));
                var svgEvent:SVGEvent = new SVGEvent(SVGEvent.SVGLoad);
                this.dispatchEvent(svgEvent);
            }
        }

        public function addActionListener(eventType:String, target:Sprite):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.addActionListener(eventType, target);
            }
            else if (Std.is( topSprite.parent, SVGViewer)) {
                cast(topSprite.parent, SVGViewer).addActionListener(eventType, target);
            }
        }

        public function removeActionListener(eventType:String, target:Sprite):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.removeActionListener(eventType, target);
            }
            else if (Std.is( topSprite.parent, SVGViewer)) {
                cast(topSprite.parent, SVGViewer).removeActionListener(eventType, target);
            }
        }
        
        public function zoomAndPan():Void {
          var m:Matrix;
          
          // do we have a cached viewBox matrix that we've already applied?
         if ( null != this._lastVBMatrix) {
            m = this._lastVBMatrix.clone();
          } else {
            m = new Matrix();
          }
                              
          m.translate(this.currentTranslate.x, this.currentTranslate.y);
          m.scale(this.currentScale, this.currentScale);
          viewBoxSprite.transform.matrix = m;
        }

        public function debug(debugString:String, ?info:PosInfos):Void {
            if (this.parentSVGRoot != null) {
                this.parentSVGRoot.debug(debugString, info);
            }
            else if (Std.is( topSprite.parent, SVGViewer)) {
                cast(topSprite.parent, SVGViewer).debug(debugString, info);
            }
        }

        public function error(message:String):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.error(message);
            }
            else if (Std.is( topSprite.parent, SVGViewer)) {
                cast(topSprite.parent, SVGViewer).error(message);
            }
        }
        
        /** Functions for profiling. */
        public override function start(subject:String, ?subjectStarted:String = null):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.start(subject, subjectStarted);
            }
            else if (Std.is( topSprite.parent, SVGViewer)) {
                cast(topSprite.parent, SVGViewer).start(subject, subjectStarted);
            }
        }
        
        /** Functions for profiling. */
        public override function end(subject:String, ?subjectStarted:String = null):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.end(subject, subjectStarted);
            }
            else if (Std.is( topSprite.parent, SVGViewer)) {
                cast(topSprite.parent, SVGViewer).end(subject, subjectStarted);
            }
        }
        
        /** Functions for profiling. */
        public override function increment(subject:String, amount:Int):Void {
           if ( null != this.parentSVGRoot) {
                this.parentSVGRoot.increment(subject, amount);
            }
            else if (Std.is( topSprite.parent, SVGViewer)) {
                cast(topSprite.parent, SVGViewer).increment(subject, amount);
            }
        }
    }
